// sshlib.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include <network.h>
#include <timing.h>
#include "bufchain.h"
#include "tree234.h"
#include "sshlib.h"
#include "ssh.h"
#include "scp.h"

#include <winsock2.h>

HMODULE hNetwork=NULL;
HMODULE hTiming=NULL;
static int pending_netevent = 0;
static WPARAM pend_netevent_wParam = 0;
static LPARAM pend_netevent_lParam = 0;
SSH_CALLBACK_PROC callback_proc[16];
HMODULE Module;
HANDLE hBackend;
tree234 *sktree;
int must_exit=0;
HWND hwnd;
HANDLE backend_idle;


void gppmap(char *def, char *val, int len)
{
    char buf[1024];
	strcpy(buf,def);
    char *p = buf;
    char *q = val;
    while (*p) {
	while (*p && *p != ',') {
	    int c = *p++;
	    if (c == '=')
		c = '\t';
	    if (c == '\\')
		c = *p++;
	    *q++ = c;
	}
	if (*p == ',')
	    p++;
	*q++ = '\0';
    }
    *q = '\0';
}


const char *const ttymodes[] = {
    "INTR",	"QUIT",     "ERASE",	"KILL",     "EOF",
    "EOL",	"EOL2",     "START",	"STOP",     "SUSP",
    "DSUSP",	"REPRINT",  "WERASE",	"LNEXT",    "FLUSH",
    "SWTCH",	"STATUS",   "DISCARD",	"IGNPAR",   "PARMRK",
    "INPCK",	"ISTRIP",   "INLCR",	"IGNCR",    "ICRNL",
    "IUCLC",	"IXON",     "IXANY",	"IXOFF",    "IMAXBEL",
    "ISIG",	"ICANON",   "XCASE",	"ECHO",     "ECHOE",
    "ECHOK",	"ECHONL",   "NOFLSH",	"TOSTOP",   "IEXTEN",
    "ECHOCTL",	"ECHOKE",   "PENDIN",	"OPOST",    "OLCUC",
    "ONLCR",	"OCRNL",    "ONOCR",	"ONLRET",   "CS7",
    "CS8",	"PARENB",   "PARODD",	NULL
};

struct keyval { char *s; int v; };

struct keyval ciphernames[] = {
    { "aes",	    CIPHER_AES },
    { "blowfish",   CIPHER_BLOWFISH },
    { "3des",	    CIPHER_3DES },
    { "WARN",	    CIPHER_WARN },
    { "arcfour",    CIPHER_ARCFOUR },
    { "des",	    CIPHER_DES }
};

struct keyval kexnames[] = {
    { "dh-gex-sha1",	    KEX_DHGEX },
    { "dh-group14-sha1",    KEX_DHGROUP14 },
    { "dh-group1-sha1",	    KEX_DHGROUP1 },
    { "WARN",		    KEX_WARN }
};

int key2val(const struct keyval *mapping, int nmaps, char *key)
{
    int i;
    for (i = 0; i < nmaps; i++)
	if (!strcmp(mapping[i].s, key)) return mapping[i].v;
    return -1;
}

void gprefs(char *def,
		   const struct keyval *mapping, int nvals,
		   int *array)
{
    char commalist[80];
    char *tokarg = commalist;
    int n;
    unsigned long seen = 0;	       /* bitmap for weeding dups etc */
	strcpy(commalist,def);

    /* Grotty parsing of commalist. */
    n = 0;
    do {
	int v;
	char *key;
	key = strtok(tokarg, ","); /* sorry */
	tokarg = NULL;
	if (!key) break;
	if (((v = key2val(mapping, nvals, key)) != -1) &&
	    !(seen & 1<<v)) {
	    array[n] = v;
	    n++;
	    seen |= 1<<v;
	}
    } while (n < nvals);
    /* Add any missing values (backward compatibility ect). */
    {
	int i;
	for (i = 0; i < nvals; i++) {
	    if (!(seen & 1<<mapping[i].v)) {
		array[n] = mapping[i].v;
		n++;
	    }
	}
    }
}

#ifdef _MANAGED
#pragma managed(push, off)
#endif

NEW_CONNECTION_PROC new_connection=NULL;
NAME_LOOKUP_PROC name_lookup=NULL;
SELECT_RESULT_PROC select_result=NULL;
NET_PENDING_ERRORS_PROC net_pending_errors=NULL;
SK_ADDR_FREE_PROC sk_addr_free=NULL;
SK_GETADDR_PROC sk_getaddr=NULL;
SK_WRITE_PROC sk_write=NULL;
SK_SET_FROZEN_PROC sk_set_frozen=NULL;
SK_CLOSE_PROC sk_close=NULL;
SK_ADDR_ERROR_PROC sk_addr_error=NULL;
SK_SOCKET_ERROR_PROC sk_socket_error=NULL;
SSH_INPUT_PROC get_userpass_input=NULL;
SCHEDULE_TIMER_PROC schedule_timer=NULL;
RUN_TIMERS_PROC run_timers=NULL;
EXPIRE_TIMER_CONTEXT_PROC expire_timer_context=NULL;
GET_NEXT_TIME_PROC get_next_time=NULL;
TIMER_CHANGE_NOTIFY_PROC timer_change_notify=NULL;
GET_SOCKET_PARAM_PROC get_socket_param=NULL;
NOTIFY_REMOTE_EXIT_PROC notify_remote_exit=NULL;


#ifdef __cplusplus    // If used by C++ code, 
extern "C" {          // we need to export the C interface
#endif

__declspec(dllexport) BOOL register_callback(int flag, SSH_CALLBACK_PROC proc)
{
		if (flag & SSH_CALLBACK_CHANNEL_DATA)
			callback_proc[CHANNEL_DATA_INDEX]=proc;
		if (flag & SSH_CALLBACK_DISCONNECT)
			callback_proc[DISCONNECT_INDEX]=proc;
		if (flag & SSH_CALLBACK_BANNER)
			callback_proc[BANNER_INDEX]=proc;
		if (flag & SCP_CALLBACK_DIRNAMES)
			callback_proc[SCP_DIRNAMES_INDEX]=proc;
		if (flag & SCP_CALLBACK_ERROR)
			callback_proc[SCP_ERROR_INDEX]=proc;
		if (flag & SCP_CALLBACK_STAT)
			callback_proc[SCP_STAT_INDEX]=proc;
		if (flag & SCP_CALLBACK_FDATA)
			callback_proc[SCP_FDATA_INDEX]=proc;
		if (flag & SCP_CALLBACK_SHUTDOWN)
			callback_proc[SCP_SHUTDOWN_INDEX]=proc;
		if (flag & SCP_CALLBACK_COMPLETED)
			callback_proc[SCP_COMPLETED_INDEX]=proc;
		if (!flag) return FALSE;
	return TRUE;
}


__declspec(dllexport) BOOL register_input(SSH_INPUT_PROC proc)
{
	get_userpass_input=proc;
	return TRUE;
}

__declspec(dllexport) void *get_backend(int SCP)
{
	if (SCP) return &scp_backend;
	return &ssh_backend;
}

__declspec(dllexport) void init_config(Config *cfg)
{
    int i;
    cfg->ssh_subsys = 0;	       /* FIXME: load this properly */
    cfg->remote_cmd_ptr = NULL;
    cfg->remote_cmd_ptr2 = NULL;
    cfg->ssh_nc_host[0] = '\0';
	strncpy(cfg->host,"", sizeof(cfg->host));
    cfg->logomitpass=1;
    cfg->logomitdata=0;

	    cfg->protocol = PROT_SSH;
	    cfg->port=22;

    /* Address family selection */
    cfg->addressfamily=ADDRTYPE_UNSPEC;

    /* The CloseOnExit numbers are arranged in a different order from
     * the standard FORCE_ON / FORCE_OFF / AUTO. */
    i=1; cfg->close_on_exit = (i+1)%3;
    cfg->warn_on_close=1;
	/* This is two values for backward compatibility with 0.50/0.51 */
	int pingmin, pingsec;
	pingmin=0;
	pingsec=0;
	cfg->ping_interval = pingmin * 60 + pingsec;
    cfg->tcp_nodelay=1;
    cfg->tcp_keepalives=0;
    strncpy(  cfg->termtype,"xterm",
	 sizeof(cfg->termtype));
    strncpy(  cfg->termspeed,"38400,38400",
	 sizeof(cfg->termspeed));
	/* This hardcodes a big set of defaults in any new saved
	 * sessions. Let's hope we don't change our mind. */
	char str[1024];
	strcpy(str,"");
	/* Default: all set to "auto" */
	for (i = 0; ttymodes[i]; i++) {
	    sprintf(str,"%s%s=A,", str, ttymodes[i]);
	}
	gppmap(str,
	       cfg->ttymodes, sizeof(cfg->ttymodes));

    gppmap("", cfg->environmt, sizeof(cfg->environmt));
    strncpy(  cfg->username, "",sizeof(cfg->username));
    cfg->nopty=0;
    cfg->compression=0;
    cfg->change_username=0;
    gprefs("\0",
	   ciphernames, CIPHER_MAX, cfg->ssh_cipherlist);
    {
	char *default_kexes;
	i=0; i = 2-i;
	if (i == FORCE_ON)
	    default_kexes = "dh-group14-sha1,dh-group1-sha1,WARN,dh-gex-sha1";
	else
	    default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,WARN";
	gprefs(default_kexes,
	       kexnames, KEX_MAX, cfg->ssh_kexlist);
    }
    cfg->ssh_rekey_time=60;
    strncpy(  cfg->ssh_rekey_data,"1G",
	 sizeof(cfg->ssh_rekey_data));
    cfg->sshprot=2;
    cfg->ssh2_des_cbc=0;
    cfg->ssh_no_userauth=0;
    cfg->try_tis_auth=0;
    cfg->try_ki_auth=1;
    cfg->ssh_no_shell=0;
    strcpy(cfg->keyfile.path,"");
    strncpy(  cfg->remote_cmd,"",
	 sizeof(cfg->remote_cmd));
    i=2*TICKSPERSEC;
    cfg->width=80;
    cfg->height=24;

	i=0; cfg->sshbug_ignore1 = 2-i;
    cfg->sshbug_plainpw1 = 2-i;
    cfg->sshbug_rsa1 = 2-i;
	cfg->sshbug_hmac2 = 2-i;
	if (cfg->sshbug_hmac2 == AUTO) {
	    if (i == 1)
		cfg->sshbug_hmac2 = FORCE_ON;
	}
    cfg->sshbug_derivekey2 = 2-i;
    cfg->sshbug_rsapad2 = 2-i;
    cfg->sshbug_pksessid2 = 2-i;
    cfg->sshbug_rekey2 = 2-i;
}
#ifdef __cplusplus
}
#endif

/*
 * Actually do the job requested by a WM_NETEVENT
 */
static void enact_pending_netevent(Socket s)
{
    static int reentering = 0;

    if (reentering)
	return;			       /* don't unpend the pending */

    pending_netevent = FALSE;

    reentering = 1;
    select_result(pend_netevent_wParam, pend_netevent_lParam,s);
    reentering = 0;
}

int cmpfortree(void *av, void *bv)
{
	socket_atom *bz=(socket_atom*)bv;
	unsigned long as = (unsigned long) av, bs = (unsigned long) bz->s;
    if (as < bs)
	return -1;
    if (as > bs)
	return +1;
    return 0;
}

int cmpforsearch(void *av, void *bv)
{
    socket_atom *b = (socket_atom*) bv;
	unsigned long as = (unsigned long) av, bs = (unsigned long) b->wParam;
    if (as < bs)
	return -1;
    if (as > bs)
	return +1;
    return 0;
}

static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
				WPARAM wParam, LPARAM lParam)
{
    static int ignore_clip = FALSE;
    static int need_backend_resize = FALSE;
    static int fullscr_on_max = FALSE;
    static UINT last_mousemove = 0;
	socket_atom *s;

    switch (message) {
      case WM_TIMER:
	if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
	    long next;

	    KillTimer(hwnd, TIMING_TIMER_ID);
	    if (run_timers(get_next_time(), &next)) {
		timer_change_notify(next, hwnd);
	    } else {
	    }
	}
	return 0;
      case WM_NETEVENT:
	/* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
	 * but the only one that's likely to try to overload us is FD_READ.
	 * This means buffering just one is fine.
	 */
    s = (socket_atom*)find234(sktree, (void *) wParam, cmpforsearch);
    if (!s)
	return 1;		       /* boggle */
		  
	if (pending_netevent)
		enact_pending_netevent(s->s);

	pending_netevent = TRUE;
	pend_netevent_wParam = wParam;
	pend_netevent_lParam = lParam;
	if (WSAGETSELECTEVENT(lParam) != FD_READ)
		enact_pending_netevent(s->s);

	net_pending_errors(s->s);
	}

    /*
     * Any messages we don't process completely above are passed through to
     * DefWindowProc() for default processing.
     */
    return DefWindowProc(hwnd, message, wParam, lParam);
}


DWORD WINAPI MainBackendProc(LPVOID param)
{
	WNDCLASS wndclass;
    MSG msg;

	wndclass.style = 0;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = Module;
	wndclass.hIcon = NULL;
	wndclass.hCursor = NULL;
	wndclass.hbrBackground = NULL;
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = L"sshdllLogBox";
	RegisterClass(&wndclass);

	int winmode; winmode= WS_DLGFRAME ;
	int exwinmode;exwinmode = 0;
	socket_atom *s;

	hwnd = CreateWindowEx(exwinmode, L"sshdllLogBox", L"sshdllLogBox1",
		winmode, CW_USEDEFAULT, CW_USEDEFAULT,
		1, 1,
		NULL, NULL, Module, NULL);

	/* here is a semaphore indicating backend status, initially signaled - can send data */
	backend_idle = CreateEvent(NULL,FALSE,TRUE,NULL);

	while (1) {

		int n = MsgWaitForMultipleObjects(0, NULL, FALSE, INFINITE,
						  QS_ALLINPUT);

		WaitForSingleObject(backend_idle,INFINITE);		/* if there is something being transferred, we should wait */

		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			if (msg.message == WM_QUIT)
			goto finished;	       /* two-level break */

			DispatchMessage(&msg);

			s = (socket_atom*)find234(sktree, (void *) pend_netevent_wParam, cmpforsearch);

			if (pending_netevent)
				enact_pending_netevent(s->s);

		}
		SetEvent(backend_idle);	/* all messages have been processed - can accept data from frontend */
    }

finished:
    DestroyWindow(hwnd);
	return 0;
}


#define GET_WINSOCK_FUNCTION(type, name) \
    name = hNetwork ? (type) GetProcAddress(hNetwork, #name) : NULL

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	
	switch (ul_reason_for_call) {
		case DLL_PROCESS_ATTACH:
			Module=hModule;
			hNetwork=LoadLibrary(L"network.dll");
			if (hNetwork==NULL) return FALSE;
			GET_WINSOCK_FUNCTION(NEW_CONNECTION_PROC,new_connection);
			GET_WINSOCK_FUNCTION(NAME_LOOKUP_PROC,name_lookup);
			GET_WINSOCK_FUNCTION(SELECT_RESULT_PROC,select_result);
			GET_WINSOCK_FUNCTION(NET_PENDING_ERRORS_PROC,net_pending_errors);
			GET_WINSOCK_FUNCTION(SK_ADDR_FREE_PROC, sk_addr_free);
			GET_WINSOCK_FUNCTION(SK_GETADDR_PROC,sk_getaddr);
			GET_WINSOCK_FUNCTION(SK_WRITE_PROC,sk_write);
			GET_WINSOCK_FUNCTION(SK_SET_FROZEN_PROC,sk_set_frozen);
			GET_WINSOCK_FUNCTION(SK_CLOSE_PROC,sk_close);
			GET_WINSOCK_FUNCTION(SK_ADDR_ERROR_PROC,sk_addr_error);
			GET_WINSOCK_FUNCTION(SK_SOCKET_ERROR_PROC,sk_socket_error);
			GET_WINSOCK_FUNCTION(SK_SOCKET_ERROR_PROC,sk_socket_error);
			GET_WINSOCK_FUNCTION(GET_SOCKET_PARAM_PROC,get_socket_param);
			sktree = newtree234(cmpfortree);

			hTiming=LoadLibrary(L"timing.dll");
			if (hTiming==NULL) return FALSE;

			schedule_timer = hTiming ? (SCHEDULE_TIMER_PROC) GetProcAddress(hTiming, "schedule_timer") : NULL;
			run_timers = hTiming ? (RUN_TIMERS_PROC) GetProcAddress(hTiming, "run_timers") : NULL;
			expire_timer_context = hTiming ? (EXPIRE_TIMER_CONTEXT_PROC) GetProcAddress(hTiming, "expire_timer_context") : NULL;
			get_next_time = hTiming ? (GET_NEXT_TIME_PROC) GetProcAddress(hTiming, "get_next_time") : NULL;
			timer_change_notify = hTiming ? (TIMER_CHANGE_NOTIFY_PROC) GetProcAddress(hTiming, "timer_change_notify") : NULL;

			hBackend=CreateThread(NULL,0,MainBackendProc,NULL,NORMAL_PRIORITY_CLASS|SYNCHRONIZE ,NULL);

			memset(callback_proc,0,8*sizeof(SSH_CALLBACK_PROC));

			break;
		case DLL_PROCESS_DETACH:
			WaitForSingleObject(hBackend,INFINITE);
			 socket_atom* s;
			if (sktree) {
			for (int i = 0; (s = (socket_atom*)index234(sktree, i)) != NULL; i++) {
				sk_close(s->s);
				Ssh ssh=s->ssh;
				delete s;
			}
			freetree234(sktree);
			sktree = NULL;
			}


			if (hNetwork) FreeLibrary(hNetwork);
			if (hTiming) FreeLibrary(hTiming);
			break;
	}

	return TRUE;
}

#ifdef _MANAGED
#pragma managed(pop)
#endif

